<template>
  <div id="sider-scene-attr" v-if="isSelectObject3D">
    <!-- type -->
    <div class="sider-scene-attr-item">
      <span>类型</span>
      <div>{{ objectData.type }}</div>
    </div>
    <!-- uuid -->
    <div class="sider-scene-attr-item">
      <span>识别码</span>
      <div class="flex items-center">
        <n-tooltip trigger="hover">
          <template #trigger>
            <span class="uuid">{{ objectData.uuid }}</span>
          </template>
          {{ objectData.uuid }}
        </n-tooltip>
        <n-button
          size="small"
          quaternary
          circle
          type="primary"
          v-if="objectData.uuid"
          @click="update('uuid')"
        >
          <template #icon>
            <n-icon size="16">
              <ReloadCircleOutline />
            </n-icon>
          </template>
        </n-button>
      </div>
    </div>
    <!-- name -->
    <div class="sider-scene-attr-item">
      <span>名称</span>
      <div>
        <n-input
          v-model:value="objectData.name"
          type="text"
          size="small"
          @update:value="update('name')"
        />
      </div>
    </div>
    <!-- position -->
    <div class="sider-scene-attr-item">
      <span>位置</span>
      <div class="flex">
        <EsInputNumber
          v-model:value="objectData.position.x"
          size="tiny"
          :show-button="false"
          :decimal="3"
          @change="update('position')"
        />
        <EsInputNumber
          v-model:value="objectData.position.y"
          size="tiny"
          :show-button="false"
          :decimal="3"
          @change="update('position')"
        />
        <EsInputNumber
          v-model:value="objectData.position.z"
          size="tiny"
          :show-button="false"
          :decimal="3"
          @change="update('position')"
        />
      </div>
    </div>
    <!-- rotation -->
    <div class="sider-scene-attr-item" v-if="transformRowsVisible.rotation">
      <span>旋转</span>
      <div class="flex">
        <EsInputNumber
          v-model:value="objectData.rotation.x"
          size="tiny"
          :decimal="2"
          :show-button="false"
          @change="update('rotation')"
          unit="°"
        />
        <EsInputNumber
          v-model:value="objectData.rotation.y"
          size="tiny"
          :decimal="2"
          :show-button="false"
          @change="update('rotation')"
          unit="°"
        />
        <EsInputNumber
          v-model:value="objectData.rotation.z"
          size="tiny"
          :decimal="2"
          :show-button="false"
          @change="update('rotation')"
          unit="°"
        />
      </div>
    </div>
    <!-- scale -->
    <div class="sider-scene-attr-item" v-if="transformRowsVisible.scale">
      <span>缩放</span>
      <div class="flex">
        <EsInputNumber
          v-model:value="objectData.scale.x"
          size="tiny"
          :decimal="3"
          :show-button="false"
          @change="update('scale')"
        />
        <EsInputNumber
          v-model:value="objectData.scale.y"
          size="tiny"
          :decimal="3"
          :show-button="false"
          @change="update('scale')"
        />
        <EsInputNumber
          v-model:value="objectData.scale.z"
          size="tiny"
          :decimal="3"
          :show-button="false"
          @change="update('scale')"
        />
      </div>
    </div>
    <!-- fov -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.fov">
      <span>视角</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.fov"
          size="tiny"
          :decimal="2"
          :show-button="false"
          @change="update('fov')"
        />
      </div>
    </div>
    <!-- left -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.left">
      <span>左</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.left"
          size="tiny"
          :show-button="false"
          @change="update('left')"
        />
      </div>
    </div>
    <!-- right -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.right">
      <span>右</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.right"
          size="tiny"
          :show-button="false"
          @change="update('right')"
        />
      </div>
    </div>
    <!-- top -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.top">
      <span>上</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.top"
          size="tiny"
          :show-button="false"
          @change="update('top')"
        />
      </div>
    </div>
    <!-- bottom -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.bottom">
      <span>下</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.bottom"
          size="tiny"
          :show-button="false"
          @change="update('bottom')"
        />
      </div>
    </div>
    <!-- near -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.near">
      <span>近点</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.near"
          size="tiny"
          :decimal="2"
          :min="0"
          :show-button="false"
          @change="update('near')"
        />
      </div>
    </div>
    <!-- far -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.far">
      <span>远点</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.far"
          size="tiny"
          :decimal="2"
          :show-button="false"
          @change="update('far')"
        />
      </div>
    </div>
    <!-- intensity -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.intensity">
      <span>强度</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.intensity"
          size="tiny"
          :min="0"
          :show-button="false"
          @change="update('intensity')"
        />
      </div>
    </div>
    <!-- color -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.color">
      <span>颜色</span>
      <div>
        <n-color-picker
          v-model:value="objectData.color"
          :show-alpha="false"
          size="small"
          @update:value="update('color')"
        />
      </div>
    </div>
    <!-- groundcolor -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.groundColor">
      <span>基色</span>
      <div>
        <n-color-picker
          v-model:value="objectData.groundColor"
          :modes="['hex']"
          :show-alpha="false"
          size="small"
          @update:value="update('groundColor')"
        />
      </div>
    </div>
    <!-- distance -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.distance">
      <span>距离</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.distance"
          size="tiny"
          :min="0"
          :show-button="false"
          @change="update('distance')"
        />
      </div>
    </div>
    <!-- angle -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.angle">
      <span>角度</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.angle"
          size="tiny"
          :min="0"
          :max="Math.PI / 2"
          :show-button="false"
          :decimal="3"
          @change="update('angle')"
        />
      </div>
    </div>
    <!-- penumbra -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.penumbra">
      <span>边缘</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.penumbra"
          size="tiny"
          :min="0"
          :max="1"
          :show-button="false"
          :decimal="2"
          @change="update('penumbra')"
        />
      </div>
    </div>
    <!-- decay -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.decay">
      <span>衰减</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.decay"
          size="tiny"
          :min="0"
          :max="Infinity"
          :show-button="false"
          @change="update('decay')"
        />
      </div>
    </div>
    <!-- shadow -->
    <div class="sider-scene-attr-item" v-if="objectDataVisible.castShadow">
      <span>阴影</span>
      <div>
        <n-checkbox
          v-model:checked="objectData.castShadow"
          @update:checked="update('castShadow')"
          >产生
        </n-checkbox>
        <n-checkbox
          v-model:checked="objectData.receiveShadow"
          @update:checked="update('receiveShadow')"
          v-if="objectDataVisible.receiveShadow"
        >
          接受
        </n-checkbox>
      </div>
    </div>

    <template v-if="objectDataVisible.shadow">
      <!-- shadowBias -->
      <div class="sider-scene-attr-item">
        <span>阴影偏移</span>
        <div>
          <EsInputNumber
            v-model:value="objectData.shadow.bias"
            size="tiny"
            :show-button="false"
            :step="0.0001"
            @change="update('shadowBias')"
          />
        </div>
      </div>
      <!-- shadowNormalBias -->
      <div class="sider-scene-attr-item">
        <span>阴影法线偏移</span>
        <div>
          <EsInputNumber
            v-model:value="objectData.shadow.normalBias"
            size="tiny"
            :show-button="false"
            @change="update('shadowNormalBias')"
          />
        </div>
      </div>
      <!-- shadowRadius -->
      <div class="sider-scene-attr-item">
        <span>阴影半径</span>
        <div>
          <EsInputNumber
            v-model:value="objectData.shadow.radius"
            size="tiny"
            :show-button="false"
            @change="update('shadowRadius')"
          />
        </div>
      </div>
    </template>

    <!-- visible -->
    <div class="sider-scene-attr-item">
      <span>可见性</span>
      <div>
        <n-checkbox
          v-model:checked="objectData.visible"
          @update:checked="update('visible')"
        />
      </div>
    </div>
    <!-- frustumcull -->
    <div class="sider-scene-attr-item">
      <span>视锥体裁剪</span>
      <div>
        <n-checkbox
          v-model:checked="objectData.frustumCulled"
          @update:checked="update('frustumCulled')"
        />
      </div>
    </div>
    <!-- renderorder -->
    <div class="sider-scene-attr-item">
      <span>渲染次序</span>
      <div>
        <EsInputNumber
          v-model:value="objectData.renderOrder"
          size="tiny"
          :show-button="false"
          @change="update('renderOrder')"
        />
      </div>
    </div>
    <!-- userdata -->
    <div class="sider-scene-attr-item">
      <span>自定义数据</span>
      <div>
        <n-input
          type="textarea"
          :value="objectData.userData"
          round
          :rows="5"
          readonly
          :status="userDataStatus"
          @click.stop="handleUserDataClick"
        />
      </div>
    </div>

    <UserData
      v-model:show="userDataEditorShow"
      @update:show="(s) => (userDataEditorShow = s)"
      v-model:value="objectData.userData"
      @update:value="update('userData')"
    />
  </div>
  <n-result
    v-else
    status="418"
    title="Empty"
    :description="t('prompt[\'No object selected.\']')"
  />
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted } from "vue";
import { useAddSignal } from "@/hooks/useSignal";
import { ReloadCircleOutline } from "@vicons/ionicons5";
import EsInputNumber from "@/components/es/EsInputNumber.vue";
import * as THREE from "three";
import { SetUuidCommand } from "@/core/commands/SetUuidCommand";
import { SetValueCommand } from "@/core/commands/SetValueCommand";
import { SetPositionCommand } from "@/core/commands/SetPositionCommand";
import { SetRotationCommand } from "@/core/commands/SetRotationCommand";
import { SetScaleCommand } from "@/core/commands/SetScaleCommand";
import { SetColorCommand } from "@/core/commands/SetColorCommand";
import { t } from "@/language";
import UserData from "@/components/code/UserData.vue";

const isSelectObject3D = ref(false);
const objectData = reactive({
  type: "",
  uuid: "",
  name: "",
  position: {
    x: 0,
    y: 0,
    z: 0,
  },
  rotation: {
    x: 0,
    y: 0,
    z: 0,
  },
  scale: {
    x: 1,
    y: 1,
    z: 1,
  },
  fov: 0,
  left: 0,
  right: 0,
  top: 0,
  bottom: 0,
  near: 0,
  far: 0,
  intensity: 0,
  color: "",
  groundColor: "",
  distance: 0,
  angle: 0,
  penumbra: 0,
  decay: 0,
  castShadow: false,
  receiveShadow: false,
  shadow: {
    bias: 0,
    normalBias: 0,
    radius: 1,
  },
  visible: true,
  frustumCulled: true,
  renderOrder: 0,
  userData: "{}",
});
const transformRowsVisible = reactive({
  rotation: true,
  scale: true,
});
const objectDataVisible = reactive({
  fov: true,
  left: true,
  right: true,
  top: true,
  bottom: true,
  near: true,
  far: true,
  intensity: true,
  color: true,
  groundColor: true,
  distance: true,
  angle: true,
  penumbra: true,
  decay: true,
  castShadow: true,
  receiveShadow: true,
  shadow: true,
});

onMounted(() => {
  useAddSignal("objectSelected", (object) => {
    if (object !== null) {
      isSelectObject3D.value = true;
      updateRows(object);
      updateUI(object);
    } else {
      isSelectObject3D.value = false;
    }
  });

  useAddSignal("objectChanged", (object) => {
    if (object !== window.editor.selected) return;
    updateUI(object);
  });
});

function updateRows(object) {
  for (const property in objectDataVisible) {
    objectDataVisible[property] = object[property] !== undefined;
  }

  if (object.isLight) {
    objectDataVisible.receiveShadow = false;
  }

  if (object.isAmbientLight || object.isHemisphereLight) {
    objectDataVisible.castShadow = false;
  }
}

function updateUI(object) {
  objectData.type = object.type;
  objectData.uuid = object.uuid;
  objectData.name = object.name;
  objectData.position.x = Number(object.position.x.toFixed(3));
  objectData.position.y = Number(object.position.y.toFixed(3));
  objectData.position.z = Number(object.position.z.toFixed(3));
  objectData.rotation.x = object.rotation.x * THREE.MathUtils.RAD2DEG;
  objectData.rotation.y = object.rotation.y * THREE.MathUtils.RAD2DEG;
  objectData.rotation.z = object.rotation.z * THREE.MathUtils.RAD2DEG;
  objectData.scale.x = Number(object.scale.x.toFixed(3));
  objectData.scale.y = Number(object.scale.y.toFixed(3));
  objectData.scale.z = Number(object.scale.z.toFixed(3));

  if (object.fov !== undefined) {
    objectData.fov = object.fov;
  }
  if (object.left !== undefined) {
    objectData.left = object.left;
  }
  if (object.right !== undefined) {
    objectData.right = object.right;
  }
  if (object.top !== undefined) {
    objectData.top = object.top;
  }
  if (object.bottom !== undefined) {
    objectData.bottom = object.bottom;
  }
  if (object.near !== undefined) {
    objectData.near = object.near;
  }
  if (object.far !== undefined) {
    objectData.far = object.far;
  }
  if (object.intensity !== undefined) {
    objectData.intensity = object.intensity;
  }
  if (object.color !== undefined) {
    objectData.color = object.color.getStyle();
  }
  if (object.groundColor !== undefined) {
    objectData.groundColor = object.color.getStyle();
  }
  if (object.distance !== undefined) {
    objectData.distance = object.distance;
  }
  if (object.angle !== undefined) {
    objectData.angle = object.angle.toFixed(3);
  }
  if (object.penumbra !== undefined) {
    objectData.penumbra = object.penumbra;
  }
  if (object.decay !== undefined) {
    objectData.decay = object.decay;
  }
  if (object.castShadow !== undefined) {
    objectData.castShadow = object.castShadow;
  }
  if (object.receiveShadow !== undefined) {
    objectData.receiveShadow = object.receiveShadow;
  }
  if (object.shadow !== undefined) {
    objectData.shadow.bias = object.shadow.bias.toFixed(4);
    objectData.shadow.normalBias = object.shadow.normalBias;
    objectData.shadow.radius = object.shadow.radius;
  }

  objectData.visible = object.visible;
  objectData.frustumCulled = object.frustumCulled;
  objectData.renderOrder = object.renderOrder;

  try {
    objectData.userData = JSON.stringify(object.userData, null, "  ");
  } catch (error) {
    console.log(error);
  }

  updateTransformRows(object);
}

function updateTransformRows(object) {
  if (object.isLight || (object.isObject3D && object.userData.targetInverse)) {
    transformRowsVisible.rotation = false;
    transformRowsVisible.scale = false;
  } else {
    transformRowsVisible.rotation = true;
    transformRowsVisible.scale = true;
  }
}

const update = (method: string) => {
  const object = window.editor.selected;
  if (object === null) return;

  const call = {
    uuid: () => {
      const newUUID = THREE.MathUtils.generateUUID();
      objectData.uuid = newUUID;
      window.editor.execute(new SetUuidCommand(object, newUUID));
    },
    name: () => {
      window.editor.execute(
        new SetValueCommand(object, "name", objectData.name)
      );
    },
    position: () => {
      const newPosition = new THREE.Vector3(
        objectData.position.x,
        objectData.position.y,
        objectData.position.z
      );
      if (object.position.distanceTo(newPosition) >= 0.01) {
        window.editor.execute(new SetPositionCommand(object, newPosition));
      }
    },
    rotation: () => {
      const newRotation = new THREE.Euler(
        objectData.rotation.x * THREE.MathUtils.DEG2RAD,
        objectData.rotation.y * THREE.MathUtils.DEG2RAD,
        objectData.rotation.z * THREE.MathUtils.DEG2RAD
      );
      if (
        new THREE.Vector3()
          .setFromEuler(object.rotation)
          .distanceTo(new THREE.Vector3().setFromEuler(newRotation)) >= 0.01
      ) {
        window.editor.execute(
          new SetRotationCommand(object, newRotation, undefined)
        );
      }
    },
    scale: () => {
      const newScale = new THREE.Vector3(
        objectData.scale.x,
        objectData.scale.y,
        objectData.scale.z
      );
      if (object.scale.distanceTo(newScale) >= 0.01) {
        window.editor.execute(new SetScaleCommand(object, newScale, undefined));
      }
    },
    fov: () => {
      if (
        object.fov !== undefined &&
        Math.abs(object.fov - objectData.fov) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "fov", objectData.fov)
        );
        object.updateProjectionMatrix();
      }
    },
    left: () => {
      if (
        object.left !== undefined &&
        Math.abs(object.left - objectData.left) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "left", objectData.left)
        );
        object.updateProjectionMatrix();
      }
    },
    right: () => {
      if (
        object.right !== undefined &&
        Math.abs(object.right - objectData.right) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "right", objectData.right)
        );
        object.updateProjectionMatrix();
      }
    },
    top: () => {
      if (
        object.top !== undefined &&
        Math.abs(object.top - objectData.top) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "top", objectData.top)
        );
        object.updateProjectionMatrix();
      }
    },
    bottom: () => {
      if (
        object.bottom !== undefined &&
        Math.abs(object.bottom - objectData.bottom) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "bottom", objectData.bottom)
        );
        object.updateProjectionMatrix();
      }
    },
    near: () => {
      if (
        object.near !== undefined &&
        Math.abs(object.near - objectData.near) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "near", objectData.near)
        );
        if (object.isOrthographicCamera) {
          object.updateProjectionMatrix();
        }
      }
    },
    far: () => {
      if (
        object.far !== undefined &&
        Math.abs(object.far - objectData.far) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "far", objectData.far)
        );
        if (object.isOrthographicCamera) {
          object.updateProjectionMatrix();
        }
      }
    },
    intensity: () => {
      if (
        object.intensity !== undefined &&
        Math.abs(object.intensity - objectData.intensity) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "intensity", objectData.intensity)
        );
      }
    },
    color: () => {
      if (
        object.color !== undefined &&
        object.color.getHex() !== objectData.color
      ) {
        window.editor.execute(
          new SetColorCommand(object, "color", objectData.color)
        );
      }
    },
    groundColor: () => {
      if (
        object.groundColor !== undefined &&
        object.groundColor.getHex() !== objectData.groundColor
      ) {
        window.editor.execute(
          new SetColorCommand(object, "groundColor", objectData.groundColor)
        );
      }
    },
    distance: () => {
      if (
        object.distance !== undefined &&
        Math.abs(object.distance - objectData.distance) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "distance", objectData.distance)
        );
      }
    },
    angle: () => {
      if (
        object.angle !== undefined &&
        Math.abs(object.angle - objectData.angle) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "angle", objectData.angle)
        );
      }
    },
    penumbra: () => {
      if (
        object.penumbra !== undefined &&
        Math.abs(object.penumbra - objectData.penumbra) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "penumbra", objectData.penumbra)
        );
      }
    },
    decay: () => {
      if (
        object.decay !== undefined &&
        Math.abs(object.decay - objectData.decay) >= 0.01
      ) {
        window.editor.execute(
          new SetValueCommand(object, "decay", objectData.decay)
        );
      }
    },
    castShadow: () => {
      if (
        object.castShadow !== undefined &&
        object.castShadow !== objectData.castShadow
      ) {
        window.editor.execute(
          new SetValueCommand(object, "castShadow", objectData.castShadow)
        );
      }
    },
    receiveShadow: () => {
      if (object.receiveShadow !== objectData.receiveShadow) {
        if (object.material !== undefined) object.material.needsUpdate = true;
        window.editor.execute(
          new SetValueCommand(object, "receiveShadow", objectData.receiveShadow)
        );
      }
    },
    shadowBias: () => {
      if (
        object.shadow !== undefined &&
        object.shadow.bias !== objectData.shadow.bias
      ) {
        window.editor.execute(
          new SetValueCommand(object.shadow, "bias", objectData.shadow.bias)
        );
      }
    },
    shadowNormalBias: () => {
      if (
        object.shadow !== undefined &&
        object.shadow.normalBias !== objectData.shadow.normalBias
      ) {
        window.editor.execute(
          new SetValueCommand(
            object.shadow,
            "normalBias",
            objectData.shadow.normalBias
          )
        );
      }
    },
    shadowRadius: () => {
      if (
        object.shadow !== undefined &&
        object.shadow.radius !== objectData.shadow.radius
      ) {
        window.editor.execute(
          new SetValueCommand(object.shadow, "radius", objectData.shadow.radius)
        );
      }
    },
    visible: () => {
      if (object.visible !== objectData.visible) {
        window.editor.execute(
          new SetValueCommand(object, "visible", objectData.visible)
        );
      }
    },
    frustumCulled: () => {
      if (object.frustumCulled !== objectData.frustumCulled) {
        window.editor.execute(
          new SetValueCommand(object, "frustumCulled", objectData.frustumCulled)
        );
      }
    },
    renderOrder: () => {
      if (object.renderOrder !== objectData.renderOrder) {
        window.editor.execute(
          new SetValueCommand(object, "renderOrder", objectData.renderOrder)
        );
      }
    },
    userData: () => {
      try {
        const userData = JSON.parse(objectData.userData);
        if (JSON.stringify(object.userData) != JSON.stringify(userData)) {
          window.editor.execute(
            new SetValueCommand(object, "userData", userData)
          );
        }
      } catch (exception) {
        console.warn(exception);
      }
    },
  };

  call[method]();
};

const userDataStatus = ref<any>(undefined);
const userDataEditorShow = ref(false);
const handleUserDataClick = () => {
  // try {
  //   JSON.parse(objectData.userData);
  //   userDataStatus.value = 'success';
  // } catch (error) {
  //   userDataStatus.value = 'error';
  // }
  userDataEditorShow.value = true;
};
</script>

<style lang="less" scoped>
#sider-scene-attr {
  .sider-scene-attr-item {
    display: flex;
    justify-content: space-between;
    margin: 10px 0;
    align-items: center;

    & > span {
      min-width: 80px;
    }

    & > div {
      width: 150px;
      color: rgb(165, 164, 164);
      overflow: hidden;

      .uuid {
        width: 100%;
        white-space: nowrap;
        overflow-x: auto;
        display: inline-block;
      }
    }
  }
}

:deep(.n-input__textarea) {
  font-size: 12px;
}
</style>
